package DatagramPlusPlus;


import java.io.IOException;
import java.net.DatagramSocket;
import java.util.LinkedList;



/**
 *
 * @author Rafael
 */

public class DatagramReceiver implements Runnable {

    // Controlo do Receiver
    private DatagramSocket s;
    private KeepingAlive keepingAlive;
    private DatagramPlusPlusSocket mainSocket;
    protected boolean recebendo = true;

    // Controlo de Congestao
    protected static final double alfa = 0.3;
    protected static final double beta = 0.25;

    protected double mediaRTT = 0;
    protected double desvRTT = 0;

    // Estado da ligacao
    protected Long ultimoRecebido;
    

    protected LinkedList<DatagramPlusPlusPacket> buffer;
    private DatagramPlusPlusPacket actualPack;

    public DatagramReceiver(DatagramSocket socket, DatagramPlusPlusSocket mainSocket, KeepingAlive keepingAlive)    {
        s = socket;
        this.mainSocket = mainSocket;
        this.keepingAlive = keepingAlive;
        actualPack = new DatagramPlusPlusPacket(new DatagramType(DatagramType.INIT));
    }

    public void run() {
        while(recebendo) {
            try {
                boolean valido = true;
                s.receive(actualPack.getUDPPacket());
                switch (actualPack.getTipo().getTipo()) {
                    //  Pacotes de estabelecimentos de ligacao so sao esperados
                    // no construtor do Socket
                    case DatagramType.INIT:
                        if(actualPack.getFlagAck())
                            System.out.println("ACK Recebido:\tINIT.");
                        else System.out.println("Pacote Recebido:\tINIT.");
                        System.out.println("Pacote inesperado: ignorado");
                        valido = false;
                        break;
                    case DatagramType.INIT_REPLY:
                        if(actualPack.getFlagAck())
                            System.out.println("ACK Recebido:\tINIT REPLY");
                        else System.out.println("Pacote Recebido:\tINIT REPLY");
                        System.out.println("Pacote inesperado: ignorado");
                        valido = false;
                        break;
                    case DatagramType.KEEP_ALIVE:
                        if(actualPack.getFlagAck()) {
                            // Simplismente nao recebe um pacote valido!
                            System.out.println("ACK Recebido:\tKEEP_ALIVE.");
                        } else {
                            DatagramPlusPlusPacket ack = new Ack(actualPack);
                            s.send(ack.getUDPPacket());

                            System.out.println("Pacote Recebido:\tKEEP_ALIVE.");
                        }
                        break;
                    case DatagramType.DATA:
                        if(actualPack.getFlagAck()) {
                            // Calcular o RTT(Round Trip Time)
                            long sampleRTT = System.nanoTime();
                            sampleRTT -= mainSocket.waitingAck.get(actualPack.getNumSerie());
                            // Salvaguardar antigo timeOut
                            synchronized(mainSocket.timeOut)    {
                                long old = mainSocket.timeOut;
                                double lastMedia =  mediaRTT;
                                // Actualizar timeOut
                                mediaRTT = (1-alfa)*mediaRTT + alfa*(double)sampleRTT;
                                desvRTT = (1-beta)*desvRTT + desvRTT*Math.abs(sampleRTT-mediaRTT);
                                mainSocket.timeOut = (long)(mediaRTT+4*desvRTT);

                                synchronized(mainSocket.windowSize) {
                                    if(mediaRTT+2*desvRTT >= lastMedia) {
//                                    || receiver.mediaRTT-2*receiver.desvRTT <= lastMedia){
                                        mainSocket.windowSize=(int)(0.8*mainSocket.windowSize);
                                    }else   {
                                        mainSocket.windowSize=(int)(1.2*mainSocket.windowSize);
                                    }
                                }

                                System.out.println("ACK Recebido:\tDATA.");
                                System.out.println("TimeOut de "+old+" para "+mainSocket.timeOut+".");
                            }
                        } else {
                            // Enviar ACK
                            DatagramPlusPlusPacket ack = new Ack(actualPack);
                            s.send(ack.getUDPPacket());
                            // Colocar no buffer
                            synchronized(buffer)  {
                                buffer.addFirst(actualPack.clone());
                            }
                            mainSocket.notifyAll();

                            System.out.println("Pacote Recebido:\tDATA.");
                        }
                        break;
                    case DatagramType.KILL:
                        if(actualPack.getFlagAck()) {
                            // Os ACK's dos kill so sao esperados na funcao close
                            System.out.println("ACK Recebido:\tKILL.");
                            System.out.println("Pacote inesperado: ignorado");
                        } else {
                            // Enviar ACK
                            DatagramPlusPlusPacket ack = new Ack(actualPack);
                            s.send(ack.getUDPPacket());
                            // Fechar a ligacao
                            mainSocket.close();
                            System.out.println("Pacote Recebido:\tKILL.");
                        }
                        break;
                }
                if(valido)
                    synchronized(ultimoRecebido)    {
                        ultimoRecebido = new Long(System.nanoTime());
                        keepingAlive.notifyAll();
                    }

            } catch (IOException ex) {
                System.out.println("Não foi possivel receber um pacote.");
            }
        }
        

    }

}
